package cn.edu.hznu.coursework.controller;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URLEncoder;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

import javax.annotation.Resource;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.apache.commons.io.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;

import cn.edu.hznu.coursework.entity.Course;
import cn.edu.hznu.coursework.entity.ResponseResult;
import cn.edu.hznu.coursework.entity.Student;
import cn.edu.hznu.coursework.entity.StudentWork;
import cn.edu.hznu.coursework.entity.Teacher;
import cn.edu.hznu.coursework.entity.TeacherWork;
import cn.edu.hznu.coursework.service.StudentService;
import cn.edu.hznu.coursework.service.TeacherService;

@Controller
@RequestMapping(value = "/teacher")
public class TeacherController {
	
	@Autowired
	private TeacherService teacherService;
	
	@Autowired
	private StudentService studentService;
	
	@Resource
	private ServletContext servletContext;
	
	private static final String TEACHER_FOLDER_PATH = "/upload/teacher/";
	private final DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm");
	
	@RequestMapping()
	public String getTeacherPage(HttpSession session, ModelMap model) {
		Teacher teacher = (Teacher) session.getAttribute("user");
		model.addAttribute("userName", teacher.getName());
		return "teacher/teacher.index";
	}

	@RequestMapping(value = "/work")
	public String getWorkPage(HttpSession session, ModelMap model) {
		Teacher teacher = (Teacher) session.getAttribute("user");
		model.addAttribute("userName", teacher.getName());
		return "/teacher/teacher.work";
	}
	
	@RequestMapping(value = "/courses")
	@ResponseBody
	public List<Course> getCoursesOfTeacher(HttpSession session) {
		Teacher teacher = (Teacher) session.getAttribute("user");
		return this.teacherService.findCoursesByTeacher(teacher)
				.stream()
				.map(course -> {
					Course info = new Course();
					info.setId(course.getId());
					info.setCourseName(course.getCourseName());
					info.setCourseNo(course.getCourseNo());
					return info;
				}).collect(Collectors.toList());
	}
	
	@RequestMapping(value = "/course/action/add", method = RequestMethod.POST)
	@ResponseBody
	public ResponseResult addNewCourse(
			@RequestParam(value = "courseName", required = true) String courseName,
			@RequestParam(value = "courseNo", required = true) char courseNo,
			HttpSession session) {
		Teacher teacher = (Teacher) session.getAttribute("user");
		ResponseResult result = new ResponseResult();
		result.setSucceeded(true);
		try {
			Course course = new Course();
			course.setCourseName(courseName);
			course.setCourseNo(courseNo);
			this.teacherService.publishCourse(teacher, course);
		} catch (Exception e) {
			result.setSucceeded(false);
			result.setMessage(e.getMessage());
		}
		return result;
	}
	
	@RequestMapping(value = "/course/id/{id}", method = RequestMethod.DELETE)
	@ResponseBody
	public ResponseResult deleteCourse(@PathVariable Integer id){
		ResponseResult result = new ResponseResult();
		result.setSucceeded(true);
		try {
			this.teacherService.removeCourse(id);
		} catch (Exception e) {
			result.setSucceeded(false);
			result.setMessage(e.getMessage());
		}
		return result;
	}

	@RequestMapping(value = "/course/id/{id}/teacher-works", method = RequestMethod.GET)
	@ResponseBody
	public List<TeacherWork> getTeacherWorksOfCourse(@PathVariable Integer id) {
		Course course = this.teacherService.findCourseById(id);
		if (course != null) {
			return this.teacherService.findTeacherWorkByCourse(course)
					.stream()
					.map(work -> {
						TeacherWork info = new TeacherWork();
						info.setId(work.getId());
						info.setTitle(work.getTitle());
						return info;
					}).collect(Collectors.toList());
					
		} else {
			return null;
		}
	} 
	
	@RequestMapping(value = "/teacher-work/action/add", method = RequestMethod.POST)
	@ResponseBody
	public ResponseResult addNewTeacherWork(
			@RequestParam(value = "title", required = true) String title,
			@RequestParam(value = "content", required = true) String content,
			@RequestParam(value = "attachment", required = false) MultipartFile attachment,
			@RequestParam(value = "deadline", required = true) String deadline,
			@RequestParam(value = "courseId", required = true) Integer courseId,
			HttpSession session) {

		Teacher teacher = (Teacher) session.getAttribute("user");
		ResponseResult result = new ResponseResult();
		result.setSucceeded(true);

		try {
			if(this.teacherService.findTeacherWorkByTeacher(teacher)
					.stream()
					.anyMatch(work -> work.getTitle().equals(title))) {
				throw new Exception("The teacher work named "
						+ title + "already exists.");
			}
			Course course = this.teacherService.findCourseById(courseId);
			if (course == null) {
				throw new Exception("The course does not existed.");
			}
			// Throw exception in advance if any
			Calendar deadlineDate = Calendar.getInstance();
			deadlineDate.setTime(dateFormat.parse(deadline));
			TeacherWork work = new TeacherWork();
			if(attachment != null) {
				StringBuilder courseFolderUrl = new StringBuilder();
				courseFolderUrl.append(TEACHER_FOLDER_PATH);
				courseFolderUrl.append(teacher.getNo());
				courseFolderUrl.append("/course/");
				courseFolderUrl.append(courseId);
				courseFolderUrl.append("/");
				String teacherFolderUrl = servletContext.getRealPath("/WEB-INF"
						+ courseFolderUrl);
				File teacherFolder = new File(teacherFolderUrl);
				if (!teacherFolder.exists()) {
					teacherFolder.mkdirs();
				}
				File attachedFile = new File(teacherFolder, attachment.getOriginalFilename());
				attachment.transferTo(attachedFile);
				work.setFilePath(courseFolderUrl + attachment.getOriginalFilename());
			}
			work.setTitle(title);
			work.setContent(content);
			work.setInformDate(Calendar.getInstance());
			work.setDeadline(deadlineDate);
			this.teacherService.publishTeacherWork(teacher, course, work);
		} catch (Exception e) {
			result.setSucceeded(false);
			result.setMessage(e.getMessage());
		}
		return result;
	}
	
	@RequestMapping(value = "/teacher-work/id/{id}", method = RequestMethod.GET)
	@ResponseBody
	public TeacherWork getTeacherWork(@PathVariable Integer id){
		TeacherWork info = new TeacherWork();
		Optional.ofNullable(this.teacherService.findTeacherWorkById(id))
				.ifPresent(work -> {
					info.setId(work.getId());
					info.setTitle(work.getTitle());
					info.setContent(work.getContent());
					info.setFilePath(work.getFilePath());
					info.setInformDate(work.getInformDate());
					info.setDeadline(work.getDeadline());
				});
		return info;
	}
	
	@RequestMapping(value = "/teacher-work/id/{id}", method = RequestMethod.DELETE)
	@ResponseBody
	public ResponseResult deleteTeacherWork(@PathVariable Integer id){
		
		ResponseResult result = new ResponseResult();
		result.setSucceeded(true);
		try {
			this.teacherService.removeTeacherWork(id);
		} catch (Exception e) {
			result.setSucceeded(false);
			result.setMessage(e.getMessage());
		}
		return result;
	}
	
	@RequestMapping(value = "/teacher-work/id/{id}/student-works", method = RequestMethod.GET)
	@ResponseBody
	public List<StudentWork> getStudentWorksOfTeacherWork(@PathVariable Integer id) {
		TeacherWork teacherWork = this.teacherService.findTeacherWorkById(id);
		if (teacherWork != null) {
			return this.studentService.findStudentWorksByTeacherWork(teacherWork)
					.stream()
//					.filter(Work -> Work.isSubmitted())
					.map(work -> {
						StudentWork info = new StudentWork();
						info.setId(work.getId());
						info.setSubmitted(work.isSubmitted());
						info.setFilePath(work.getFilePath());
						info.setScore(work.getScore());
						info.setSubmitDate(work.getSubmitDate());
						Optional.ofNullable(work.getStudent())
								.ifPresent(student -> {
									Student stuInfo = new Student();
									stuInfo.setId(student.getId());
									stuInfo.setNo(student.getNo());
									stuInfo.setName(student.getName());
									info.setStudent(stuInfo);
								});
						return info;
					}).collect(Collectors.toList());
		} else {
			return null;
		}
	}
	
	@RequestMapping(value = "/teacher-work/id/{id}/attachment", method = RequestMethod.GET)
	public void getTeacherWorkAttachment(@PathVariable Integer id,
			HttpServletResponse response) {
		String allPath = servletContext.getRealPath("/WEB-INF" 
			+ this.teacherService.findTeacherWorkById(id).getFilePath());
		this.constructResponseOfDownloading(allPath, response);
	}
	
	@RequestMapping(value = "/student-work/id/{id}/attachment", method = RequestMethod.GET)
	public void getStudentWorkAttachment(@PathVariable Integer id,
			HttpServletResponse response) {
		String allPath = servletContext.getRealPath("/WEB-INF" 
			+ this.studentService.findStudentWorkById(id).getFilePath());
		this.constructResponseOfDownloading(allPath, response);
	}

	@RequestMapping(value = "/student")
	public String getStudentPage(HttpSession session, ModelMap model) {
		Teacher teacher = (Teacher) session.getAttribute("user");
		model.addAttribute("userName", teacher.getName());
		return "teacher/teacher.student";
	}
	
	@RequestMapping(value = "/course/id/{id}/students", method = RequestMethod.GET)
	@ResponseBody
	public List<Student> getStudentsOfCourse(@PathVariable Integer id) {
		Course course = this.teacherService.findCourseById(id);
		if(course != null) {
			return this.studentService.findStudentsByCourses(Arrays.asList(course))
					.stream()
					.map(student -> {
						Student info = new Student();
						info.setId(student.getId());
						info.setNo(student.getNo());
						info.setName(student.getName());
						return info;
					}).collect(Collectors.toList());
		} else {
			return null;
		}
	}
	
	@RequestMapping(value = "/student/action/add", method = RequestMethod.POST)
	@ResponseBody
	public ResponseResult addStudentToCourse(
			@RequestParam(value = "no", required = true) String no,
			@RequestParam(value = "courseId", required = true) Integer courseId) {
		
		ResponseResult result = new ResponseResult();
		result.setSucceeded(true);
		try {
			Student student = this.studentService.findStudentByNo(no);
			if(student == null) {
				throw new Exception("this student does not exist.");
			}
			this.teacherService.addStudent(student, 
					this.teacherService.findCourseById(courseId));
		} catch (Exception e) {
			result.setSucceeded(false);
			result.setMessage(e.getMessage());
		}
		return result;
	}
	
	@RequestMapping(value = "/course/id/{courseId}/student/id/{studentId}", method = RequestMethod.DELETE)
	@ResponseBody
	public ResponseResult deleteStudentFromCourse(
			@PathVariable Integer courseId,
			@PathVariable Integer studentId) {
		ResponseResult result = new ResponseResult();
		result.setSucceeded(true);
		try {
			this.teacherService.removeStudent(this.studentService.findStudentById(studentId),
					this.teacherService.findCourseById(courseId));
		} catch (Exception e) {
			result.setSucceeded(false);
			result.setMessage(e.getMessage());
		}
		return result;
	}
	
	@RequestMapping(value = "/course/id/{courseId}/student/id/{studentId}/student-works", method = RequestMethod.GET)
	@ResponseBody
	public List<StudentWork> getStudentWorksOfStudent(
			@PathVariable Integer courseId,
			@PathVariable Integer studentId) {
		Student student = this.studentService.findStudentById(studentId);
		Course course = this.teacherService.findCourseById(courseId);
		if(student != null && course != null) {
			return this.studentService.findStudentWorksByStudentAndCourse(student, course)
					.stream()
					.map(work -> {
						StudentWork info = new StudentWork();
						info.setId(work.getId());
						info.setSubmitted(work.isSubmitted());
						info.setFilePath(work.getFilePath());
						info.setScore(work.getScore());
						info.setSubmitDate(work.getSubmitDate());
						Student stuInfo = new Student();
						stuInfo.setId(student.getId());
						stuInfo.setNo(student.getNo());
						stuInfo.setName(student.getName());
						info.setStudent(stuInfo);
						return info;
					}).collect(Collectors.toList());
		} else {
			return null;
		}
	}
	
	@RequestMapping(value = "/student-works/action/update", method = RequestMethod.POST)
	@ResponseBody
	public ResponseResult updateStudentWorkScore(
			@RequestParam(value = "ids", required = true) Integer[] ids,
			@RequestParam(value = "scores", required = true) int[] scores) {
		ResponseResult result = new ResponseResult();
		result.setSucceeded(true);
		List<StudentWork> works = new ArrayList<StudentWork>();
		for(int i = 0; i < ids.length; i ++) {
			StudentWork work = this.studentService.findStudentWorkById(ids[i]);
			if(work != null) {
				work.setScore(scores[i]);
				works.add(work);	
			}
		}
		try {
			this.teacherService.updateStudentWork(works);
		} catch (Exception e) {
			result.setSucceeded(false);
			result.setMessage(e.getMessage());
		}
		return result ;
	}
	
	private void constructResponseOfDownloading(String filePath, 
			HttpServletResponse response){
		File attachment = new File(filePath);
		FileInputStream inputStream = null;
		OutputStream outStream = null;
		try {
			inputStream = new FileInputStream(attachment);
			outStream = response.getOutputStream();
			response.setContentLength((int) attachment.length());
			Optional.ofNullable(servletContext.getMimeType(filePath))
					.ifPresent(mimeType -> 
					response.setContentType(mimeType));
			response.setHeader("Content-Disposition",
					String.format("attachment; filename=\"%s\"", 
							URLEncoder.encode(attachment.getName(), "UTF-8")));
			IOUtils.copy(inputStream, outStream);
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			try {
				if (null != inputStream) {
					inputStream.close();
				}
				if (null != outStream) {
					outStream.close();
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
}
